package com.zthz.douxing

import com.zthz.douxing.utils.CommonUtil
import redis.clients.jedis.Jedis
import redis.clients.jedis.JedisPool
import redis.clients.jedis.Protocol
import redis.clients.jedis.exceptions.JedisConnectionException

import javax.annotation.PostConstruct

class RedisService {

    private final $lock = new Object[0]

    def grailsApplication
    def redisPool

    def KeyPattern = /([a-zA-Z0-9]{1,20}:)*[a-zA-Z0-9]{1,20}/
    def currentIp

    // NX|XX, NX -- Only set the key if it does not already exist. XX
    //      -- Only set the key if it already exist.
    private final String NX = "NX"

    //EX|PX, expire time units: EX = seconds; PX = milliseconds
    private final String PX = "PX"

    //private final long LOCKTIME = 1000*3

    //private final long SLEEPTIME = 1000*2

    private final int MAX_TRY = 3


    /**
     *
     * 创建一个redisPool
     */
    @PostConstruct
    def init() {

        def redisConfigMap = grailsApplication.config.grails.redis ?: [:]
        def host = redisConfigMap?.host ?: 'localhost'
        def port = redisConfigMap?.port ? (redisConfigMap?.port.class == String && redisConfigMap?.port.isInteger() ? redisConfigMap?.port.toInteger() : redisConfigMap?.port) : Protocol.DEFAULT_PORT
        def timeout = redisConfigMap?.timeout ?: Protocol.DEFAULT_TIMEOUT
        def password = redisConfigMap?.password ?: null
        def database = redisConfigMap?.database ?: Protocol.DEFAULT_DATABASE
        def sentinels = redisConfigMap?.sentinels ?: null
        def masterName = redisConfigMap?.masterName ?: null
        redisPool = new JedisPool(host, port)

    }

    /**
     * 创建像一个工作队列中发送一个事件。 使用LPUSH完成
     * @param closure
     * @return
     */
    def sendTaskToQueue(queueName, taskBody){
        withRedis { Jedis redis ->
                redis.lpush(queueName, taskBody)
        }
    }

    /**
     * 是用BRPOPLPUSH 来实现可靠的方式队列。 先从自己的工作队列中去，如果没有的话就从总得获取。 这个地方在一个机器上要串行执行
     * @param queueName
     * @param timeout 默认等待10秒
     */
    def getTaskFromQueue(queueName, timeout = 10){
        if(!currentIp){
            currentIp = CommonUtil.getInet4Address()
        }
        def localKey = "${currentIp}:${queueName}"
        return withRedis { Jedis redis ->
            def resultTask = redis.lpop(localKey)
            if(resultTask){
                return resultTask
            } else {
                //需要对这一段做加锁同步。
                synchronized($lock) {
                    //当前队列中没有，从主队列中获取。
                    resultTask = redis.brpoplpush(queueName, localKey, timeout)
                    def tempTask = redis.rpop(localKey)
                    if(resultTask == tempTask){
                        return resultTask
                    } else {
                        //出错了, 如何处理。
                    }
                }
            }
        }
    }


    def withRedis(Closure closure) {
        Jedis redis = redisPool.resource
        try {
            def ret = closure(redis)
            return ret
        } catch (JedisConnectionException jce) {
            log.error(jce.message, jce)
            throw jce
        } catch (Exception e) {
            throw e
        } finally {
           redisPool.returnResource(redis)
        }
    }

    def withRedisHasParameter(Closure closure,Object condition) {
        Jedis redis = redisPool.resource
        try {
            def ret = closure(redis,condition)
            return ret
        } catch (JedisConnectionException jce) {
            log.error(jce.message, jce)
            throw jce
        } catch (Exception e) {
            throw e
        } finally {
            redisPool.returnResource(redis)
        }
    }

    void propertyMissing(String name, Object value) {
        //这个地方的异常要补货
        withRedis { Jedis redis ->
            redis.set(name, value)
        }
    }

    Object propertyMissing(String name) {
        def value = null
        withRedis { Jedis redis ->
            value = redis.get(name)
        }
        return value
    }

    public Long expire(String name, int seconds) {
        withRedis { Jedis redis ->
           redis.expire(name, seconds)
        }
    }

    public boolean isLockFail(final String key, final int expiredTime){
        String isLocked
        withRedis { Jedis redis ->
            isLocked = redis.set(key,key,NX,PX,expiredTime)
        }
        return !(isLocked?.equalsIgnoreCase("OK"))
    }

    /**
     * 尝试去锁定一个操作。
     * @param key 锁定的范围
     * @param expiredTime 超时的时间
     * @return 如果锁定成功返回true，否则返回false
     */
    def boolean tryLock(final String key, final int expiredTime){
        log.info("${key} _ ${expiredTime}")
        final long interTime = expiredTime / MAX_TRY
        return withRedis {Jedis jedis ->
            def isLockFailResult = true
            for( int i = 0 ; i < MAX_TRY && isLockFailResult; i++ ){
                isLockFailResult = isLockFail(key,expiredTime)
                if(!isLockFailResult){
                    return true
                }
                Thread.sleep(interTime)
            }
            return false
        }
    }



    public String unLock(String key){

        String srcipt = '''
            if redis.call('get',KEYS[1]) == ARGV[1]
                then
                    return redis.call('del',KEYS[1])
                else
                    return 0
                end
        '''

        withRedis {Jedis jedis ->
            jedis.eval(srcipt,1,key)
        }
    }


}
